package com.vivo.ic.dm;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Binder;
import android.os.ParcelFileDescriptor;

import com.vivo.ic.VLog;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class DownloadProvider extends ContentProvider {
    private static final String TAG = Constants.PRE_TAG + "DownloadProvider";
    private SQLiteOpenHelper mOpenHelper = null;
    
    /** URI matcher used to recognize URIs sent by applications */
    private UriMatcher mURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    private static final int ALL_DOWNLOADS = 1;
    private static final int ALL_DOWNLOADS_ID = 2;
    private static final int ALL_DOWNLOADS_HEADER = 3;
    
    @Override
    public boolean onCreate() {
        // start the DownloadService class. don't wait for the 1st download to be issued.
        // saves us by getting some initialization code in DownloadService out of the way.
        VLog.w(TAG,"DownloadProvider onCreate() begin");
        Context context = getContext();
        Downloads.Impl.setAuthor(context.getPackageName() + ".ICDM");
        mURIMatcher.addURI(Downloads.Impl.AUTHOR, "all_downloads", ALL_DOWNLOADS);
        mURIMatcher.addURI(Downloads.Impl.AUTHOR, "all_downloads/#", ALL_DOWNLOADS_ID);
        mURIMatcher.addURI(Downloads.Impl.AUTHOR, "all_downloads/#/" + Downloads.RequestHeaders.URI_SEGMENT,
                ALL_DOWNLOADS_HEADER);
        mOpenHelper = new DownloadProviderHelper(getContext());
//        DownloadManager.startDownloadService(context, "DB onCreate");
        return true;
    }
    
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        VLog.d(TAG,"query() Calling Pid: " + Binder.getCallingPid() + " selection: " + selection);
        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
        
        // 如果查询的是header表，则转向header
        int match = mURIMatcher.match(uri);
        if (match == ALL_DOWNLOADS_HEADER) {
            if (projection != null || selection != null || sortOrder != null) {
                throw new UnsupportedOperationException(
                        "Request header queries do not support " + "projections, selections or sorting");
            }
            return queryRequestHeaders(db, uri);
        }
        
        SqlSelection fullSelection = getWhereClause(uri, selection, selectionArgs, mURIMatcher.match(uri));
        
        Cursor ret = db.query(DownloadProviderHelper.DB_TABLE, projection, fullSelection.getSelection(),
                fullSelection.getParameters(), null, null, sortOrder);
        
        if (ret != null) {
            VLog.i(TAG, "query() count:" + ret.getCount());
            ret.setNotificationUri(getContext().getContentResolver(), uri);
        } else {
            VLog.w(TAG, "query failed in downloads database.", new Throwable());
        }
        
        return ret;
    }
    
    // 返回intent中用于匹配的data类型，
    // 参见：http://www.apkbus.com/thread-164405-1-1.html
    // manifest如果不包含此种data，则此方法无用
    @Override
    public String getType(Uri uri) {
        return "vnd.android.cursor.dir/download";
    }
    
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        VLog.d(TAG,"insert() Calling Pid: " + Binder.getCallingPid() + " values: " + values);
        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        
        // validate the visibility column
        Integer vis = values.getAsInteger(Downloads.Column.VISIBILITY);
        if (vis == null) {
            values.put(Downloads.Column.VISIBILITY, Downloads.Impl.VISIBILITY_VISIBLE);
        }
        
        Integer control = values.getAsInteger(Downloads.Column.CONTROL);
        if (control == null) {
            values.put(Downloads.Column.CONTROL, Downloads.Impl.CONTROL_RUN);
        }
        
        values.put(Downloads.Column.STATUS, Downloads.Impl.STATUS_PENDING);
        values.put(Downloads.Column.TOTAL_BYTES, -1);
        values.put(Downloads.Column.CURRENT_BYTES, 0);
        values.put(Downloads.Column.CURRENT_SPEED, 0);
        values.put(Downloads.Column.LAST_MODIFICATION, System.currentTimeMillis());
        values.put(Downloads.Column.COMPLETE_THREADS, 0);
        
        long rowID = db.insert(DownloadProviderHelper.DB_TABLE, null, values);
        if (rowID == -1) {
            VLog.e(TAG, "couldn't insert into downloads database");
            return null;
        }
        
        insertRequestHeaders(db, rowID, values);
        notifyContentChanged(uri);
        
        // Always start service to handle notifications and/or scanning
        final Context context = getContext();
        DownloadManager.startDownloadService(context, "DB insert");
        
        return ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, rowID);
    }
    
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        VLog.d(TAG,"delete() Calling Pid: " + Binder.getCallingPid() + " selection: " + selection);
        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        
        deleteRequestHeaders(db, selection, selectionArgs);
        
        int count;
        int match = mURIMatcher.match(uri);
        long id = 0;
        switch (match) {
        case ALL_DOWNLOADS:
        case ALL_DOWNLOADS_ID:
            SqlSelection select = getWhereClause(uri, selection, selectionArgs, match);
            
            final Cursor cursor = db.query(DownloadProviderHelper.DB_TABLE, new String[] { Downloads.Column._ID },
                    select.getSelection(), select.getParameters(), null, null, null);
            try {
                while (cursor.moveToNext()) {
                    id = cursor.getLong(0);
                    DownloadManager.getInstance().getNotifier().cancelAllNotification((int) id);
                    VLog.i(TAG, "database deleted, cancel all notification of id " + id);
                }
            } finally {
                if (cursor != null) {
                    cursor.close();
                }
            }
            count = db.delete(DownloadProviderHelper.DB_TABLE, select.getSelection(), select.getParameters());
            VLog.d(TAG, "db.delete() count: " + count);
            break;
        case ALL_DOWNLOADS_HEADER:
            throw new UnsupportedOperationException("Cannot delete header: " + uri);
        default:
            VLog.d(TAG, "updating unknown/invalid URI: " + uri);
            throw new UnsupportedOperationException("Cannot delete URI: " + uri);
        }
        if (id > 0) {
            notifyContentChanged(id);
        }
        return count;
    }
    
    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        VLog.d(TAG,"update() Calling Pid: " + Binder.getCallingPid() + " values: " + values);
        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        int count;
        boolean startService = false;
        
        Integer i = values.getAsInteger(Downloads.Column.CONTROL);
        VLog.d(TAG, "update() i " + i);
        if (i != null) {
            startService = true;
            NetworkInfo netInfo = DMUtil.getActiveNetworkInfo();
            if (i == Downloads.Impl.CONTROL_RUN && netInfo != null
                    && netInfo.getType() == ConnectivityManager.TYPE_MOBILE) {
                values.put(Downloads.Column.NETWORK_CHANGED, Constants.CHANGE_STATE_MOBILE);
            }
        }
        
        Integer status = values.getAsInteger(Downloads.Column.STATUS);
        boolean isRestart = status != null && status == Downloads.Impl.STATUS_PENDING;
        VLog.d(TAG, "update() isRestart " + isRestart);
        if (isRestart) {
            startService = true;
        }
        
//        if (status != null && status == Downloads.Impl.STATUS_PAUSED_BY_APP) {
//            values.put(Downloads.Column.CONTROL, Downloads.Impl.CONTROL_PAUSED);
//        }
        
        int match = mURIMatcher.match(uri);
        switch (match) {
        case ALL_DOWNLOADS:
        case ALL_DOWNLOADS_ID:
            SqlSelection select = getWhereClause(uri, selection, selectionArgs, match);
            count = db.update(DownloadProviderHelper.DB_TABLE, values, select.getSelection(), select.getParameters());
            VLog.d(TAG, "db.update() count: " + count);
            break;
        case ALL_DOWNLOADS_HEADER:
            throw new UnsupportedOperationException("Cannot update header: " + uri);
        default:
            VLog.d(TAG, "updating unknown/invalid URI: " + uri);
            throw new UnsupportedOperationException("Cannot update URI: " + uri);
        }
        VLog.d(TAG, "startService " + startService);
        notifyContentChanged(uri);
        if (startService) {
            Context context = getContext();
            DownloadManager.startDownloadService(context, "DB update");
        }
        return count;
    }
    
    @Override
    public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
        final Cursor cursor = query(uri, new String[] { Downloads.Column.DATA }, null, null, null);
        final String path;
        try {
            int count = (cursor != null) ? cursor.getCount() : 0;
            if (count != 1) {
                // If there is not exactly one result, throw an appropriate exception.
                if (count == 0) {
                    throw new FileNotFoundException("No entry for " + uri);
                }
                throw new FileNotFoundException("Multiple items at " + uri);
            }
            
            if (cursor.moveToFirst()) {
                path = cursor.getString(0);
            } else {
                throw new FileNotFoundException("Failed moveToFirst");
            }
        } finally {
            DMUtil.closeQuietly(cursor);
        }
        
        if (path == null) {
            throw new FileNotFoundException("No filename found.");
        }
        
        final File file = new File(path);
        
        return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE);
    }
    
    /**
     * Insert request headers for a download into the DB.
     */
    private void insertRequestHeaders(SQLiteDatabase db, long downloadId, ContentValues values) {
        ContentValues rowValues = new ContentValues();
        rowValues.put(Downloads.RequestHeaders.COLUMN_DOWNLOAD_ID, downloadId);
        for (Map.Entry<String, Object> entry : values.valueSet()) {
            String key = entry.getKey();
            if (key.startsWith(Downloads.RequestHeaders.INSERT_KEY_PREFIX)) {
                String headerLine = entry.getValue().toString();
                if (!headerLine.contains(":")) {
                    throw new IllegalArgumentException("Invalid HTTP header line: " + headerLine);
                }
                String[] parts = headerLine.split(":", 2);
                rowValues.put(Downloads.RequestHeaders.COLUMN_HEADER, parts[0].trim());
                rowValues.put(Downloads.RequestHeaders.COLUMN_VALUE, parts[1].trim());
                db.insert(Downloads.RequestHeaders.HEADERS_DB_TABLE, null, rowValues);
            }
        }
    }
    
    /**
     * Delete request headers for downloads matching the given query.
     */
    private void deleteRequestHeaders(SQLiteDatabase db, String where, String[] whereArgs) {
        String[] projection = new String[] { Downloads.Column._ID };
        Cursor cursor = db.query(DownloadProviderHelper.DB_TABLE, projection, where, whereArgs, null, null, null, null);
        try {
            for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
                long id = cursor.getLong(0);
                String idWhere = Downloads.RequestHeaders.COLUMN_DOWNLOAD_ID + "=" + id;
                db.delete(Downloads.RequestHeaders.HEADERS_DB_TABLE, idWhere, null);
            }
        } finally {
            cursor.close();
        }
    }
    
    /**
     * Notify of a change through both URIs (/my_downloads and /all_downloads)
     * 
     * @param uri
     *            either URI for the changed download(s)
     */
    private void notifyContentChanged(final Uri uri) {
        Long downloadId = null;
        if (mURIMatcher.match(uri) == ALL_DOWNLOADS_ID) {
            downloadId = Long.parseLong(getDownloadIdFromUri(uri));
            Uri uriToNotify = ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, downloadId);
            getContext().getContentResolver().notifyChange(uriToNotify, null);
        }
    }
    
    private void notifyContentChanged(long id) {
        Uri uriToNotify = ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, id);
        getContext().getContentResolver().notifyChange(uriToNotify, null);
    }
    
    private String getDownloadIdFromUri(final Uri uri) {
        return uri.getPathSegments().get(1);
    }
    
    /**
     * Handle a query for the custom request headers registered for a download.
     */
    private Cursor queryRequestHeaders(SQLiteDatabase db, Uri uri) {
        String where = Downloads.RequestHeaders.COLUMN_DOWNLOAD_ID + "=" + getDownloadIdFromUri(uri);
        String[] projection = new String[] { Downloads.RequestHeaders.COLUMN_HEADER,
                Downloads.RequestHeaders.COLUMN_VALUE };
        return db.query(Downloads.RequestHeaders.HEADERS_DB_TABLE, projection, where, null, null, null, null);
    }
    
    private SqlSelection getWhereClause(final Uri uri, final String where, final String[] whereArgs, int uriMatch) {
        SqlSelection selection = new SqlSelection();
        selection.appendClause(where, whereArgs);
        if (uriMatch == ALL_DOWNLOADS_ID) {
            selection.appendClause(Downloads.Column._ID + " = ?", getDownloadIdFromUri(uri));
        }
        return selection;
    }
    
    /**
     * This class encapsulates a SQL where clause and its parameters. It makes it possible for
     * shared methods (like {@link DownloadProvider#getWhereClause(Uri, String, String[], int)}) to
     * return both pieces of information, and provides some utility logic to ease piece-by-piece
     * construction of selections.
     */
    private static class SqlSelection {
        public StringBuilder mWhereClause = new StringBuilder();
        public List<String> mParameters = new ArrayList<String>();
        
        public <T> void appendClause(String newClause, final T... parameters) {
            if (newClause == null || newClause.isEmpty()) {
                return;
            }
            if (mWhereClause.length() != 0) {
                mWhereClause.append(" AND ");
            }
            mWhereClause.append("(");
            mWhereClause.append(newClause);
            mWhereClause.append(")");
            if (parameters != null) {
                for (Object parameter : parameters) {
                    mParameters.add(parameter.toString());
                }
            }
        }
        
        public String getSelection() {
            return mWhereClause.toString();
        }
        
        public String[] getParameters() {
            String[] array = new String[mParameters.size()];
            return mParameters.toArray(array);
        }
    }
}
